using System.Collections.Generic;
using System.Drawing;
using System;

using OhioState.AssetManaged;
using OhioState.Graphics;
using OhioState.Graphics.Proxy;
using OhioState.Math;
using Tao.OpenGl;

namespace OhioState.Graphics.OpenGL
{
    public class LayerHandler : OpenGLObject, ILayer
    {
        private LayerProxy _layerProxy;
        private LayerGL _realLayer;
        private IFrameBufferObject frameBufferObject;
        private IAssetManagedHandler updateMaterial;
        
        public LayerHandler( LayerProxy proxyObj )
            : base(proxyObj)
        {
            _layerProxy = proxyObj;
            _realLayer = new LayerGL();
            updateMaterial = new IAssetManagedHandler(_layerProxy_UpdateMaterialChangedEvent);
            _layerProxy.AddMaterialChangedEventHandler(updateMaterial);
        }
        public IMaterial Material
        {
            get { return _layerProxy.Material; }
            set { }
        }
        public bool Active
        {
            get { return _layerProxy.Active; }
            set { _layerProxy.Active = value; }
        }
        
        public ICamera Camera
        {
            get { return _layerProxy.Camera; }
            set { _layerProxy.Camera = value; }
        }
        public List<IDrawable> Drawables
        {
            get { return _layerProxy.Drawables; }
        }

        public IFrameBufferObject FrameBuffer
        {
            get { return _layerProxy.FrameBuffer; }
            set { throw new ApplicationException("Set methods should not be called on Handlers"); }
        }

        //private int _width = 128;
        //private int _height = 128;
        //public void ResizePanel(int width, int height)
        //{
        //    if (_width != width || _height != height)
        //    {
        //        _width = width;
        //        _height = height;
        //        if (_created)
        //        {
        //            (_tex2DHandler as LayerTextureHandler).Resize(_width, _height);
        //            //_renderTexture.bindTexture();
        //            _renderTexture.Enable();
        //        }
        //    }
        //}

        private bool _created = false;
        public void Render(IRenderPanel panel)
        {
            if (Active)
            {
                if (!_created)
                {
                    Create(panel);
                }
                if (this.Invalid)
                {
                    this.Invalid = false;
                    UpdateLayer(panel);
                }
                if (_materialInvalid)
                {
                    SetMaterial(panel);
                }

                _realLayer.Render(panel);
            }            
        }

        private void Create(IRenderPanel panel)
        {
            //(_tex2DHandler as LayerTextureHandler).Resize(_width, _height);
            //_renderTexture = panel.ResourceManager.GetItem(_layerProxy.FrameBufferObject as IAssetManaged) as FrameBufferObjectGL;
            _created = true;
        }

        public void UpdateLayer(IRenderPanel panel)
        {
            //Enable the framebuffer
            frameBufferObject = panel.ResourceManager.GetItem(this.FrameBuffer as IAssetManaged) as IFrameBufferObject;
            if (frameBufferObject != null)
            {
                frameBufferObject.Enable(panel);
                //_fboHandler.enableFrameBuffer();
                //Gl.glPushAttrib(Gl.GL_VIEWPORT_BIT);
                //Gl.glViewport(0, 0, _layerProxy.LayerTexProxy.Width, _layerProxy.LayerTexProxy.Height);
                //Gl.glViewport(0, 0, OpenGLPanel.Instance.Width, OpenGLPanel.Instance.Height);


                Camera camera = new Camera();
                camera.SetModelViewMatrix(_layerProxy.Camera.GetModelViewMatrix());
                camera.SetProjectionMatrix(_layerProxy.Camera.GetProjectionMatrix());

                //debug!!!
                //we cannot use projection matrix directly, because aspect ratio of "MainCamera"
                //is "incorporated" into the matrix; thus we should first multiply by inverse of 
                //"MainCamera"'s aspect ratio and then by aspect ratio of current glPanel - thus for
                //all glPanels we get proper projection matrix

                Matrix4 scale = new Matrix4();
                scale[1, 1] = 1f / _layerProxy.Camera.AspectRatio;
                camera.SetProjectionMatrix(scale * camera.GetProjectionMatrix());

                Matrix4 scale2 = new Matrix4();
                scale2[1, 1] = (float)panel.Width / (float)panel.Height;
                camera.SetProjectionMatrix(scale2 * camera.GetProjectionMatrix());

                // 
                // Set the camera for the drawables.
                //


                Gl.glMatrixMode(Gl.GL_MODELVIEW);
                Gl.glPushMatrix();
                //debug!!! - instead of using "MainCamera" use layerGL's local camera
                //Gl.glLoadMatrixf(Camera.GetModelViewMatrix().GetStreamColumnOrder());
                Gl.glLoadMatrixf(camera.GetModelViewMatrix().GetStreamColumnOrder());

                Gl.glMatrixMode(Gl.GL_PROJECTION);
                Gl.glPushMatrix();
                //debug!!! - instead of using "MainCamera" use layerGL's local camera
                //Gl.glLoadMatrixf(Camera.GetProjectionMatrix().GetStreamColumnOrder());
                Gl.glLoadMatrixf(camera.GetProjectionMatrix().GetStreamColumnOrder());


                //Loop thru each drawable and render it into the texture.

                for (int i = 0; i < _layerProxy.Drawables.Count; i++)
                {
                    IDrawable drawProxy = _layerProxy.Drawables[i];
                    //foreach (IDrawable drawProxy in _layerProxy.Drawables)
                    //{
                    IDrawable drawGL = panel.ResourceManager.GetItem(drawProxy as IAssetManaged) as IDrawable;
                    if (drawGL != null)
                    {
                        //try
                        {
                            drawGL.Render(panel);
                        }
                        //catch { }
                    }
                }


                //_layerProxy.Image = copyToBitmap();
                // Disable the render target (frame-buffer object).
                frameBufferObject.Disable(panel);
                //_fboHandler.disableFrameBuffer();
                // Reset the camera.
                Gl.glMatrixMode(Gl.GL_PROJECTION);
                Gl.glPopMatrix();
                Gl.glMatrixMode(Gl.GL_MODELVIEW);
                Gl.glPopMatrix();
                //Gl.glPopAttrib();
            }
        }

        private bool _materialInvalid = true;
        public void _layerProxy_UpdateMaterialChangedEvent(IAssetManaged obj)
        {
            _materialInvalid = true;
        }

        private void SetMaterial(IRenderPanel panel)
        {
            IMaterial material = panel.ResourceManager.GetItem(_layerProxy.Material as IAssetManaged) as IMaterial;
            if (material != null)
            {
                _realLayer.Material = material;
            }
            _materialInvalid = false;
        }

        /// overriding OpenGLObject method is necessary - 
        /// we should unregister _realLayer's changeHandler
        /// manually here, as well as materialChangeHandler
        public override void Dispose()
        {
            base.Dispose();
            if (proxyObject is LayerProxy)
            {
                _layerProxy.RemoveMaterialChangedEventHandler(updateMaterial);
            }
        }

        //public void _layerProxy_UpdateBitmapChangedEvent(Bitmap obj)
        //{
        //    _layerProxy.Image = obj;
        //}

        //protected void invalidateMaterialState(OhioState.Utility.IAssetManaged obj)
        //{
        //    this.ValidMaterial = false;
        //}

        // TODO: This should be moved to a separate class
        #region FileIO

        private void saveTexture(byte[] m_pTexBuffer, int width, int height)
        {
            throw new ApplicationException("This code should be moved to the Texture2D class or a variant");

            //_tex2DHandler.bind();
            //Gl.glGetTexImage(Gl.GL_TEXTURE_2D, 0, Gl.GL_RGBA, Gl.GL_UNSIGNED_BYTE, m_pTexBuffer);
            //int error = Gl.glGetError();
            //_tex2DHandler.unbind();
        }
        public Bitmap copyToBitmap()
        {
            throw new ApplicationException("This code should be moved to the Texture2D class or a variant");

        //    int width = _tex2DHandler.Width;
        //    int height = _tex2DHandler.Height;

        //    byte[] m_pTexBuffer = new byte[width * height * 4];
        //    for (int i = 0; i < width * height * 4; i++)
        //        m_pTexBuffer[i] = 255;

        //    saveTexture(m_pTexBuffer, width, height);
        //    Bitmap bm = new Bitmap(width, height);

        //    byte r, g, b, a;
        //    for(int j = 0; j < height; j++)
        //    {
        //        for (int i = 0; i < width; i++)
        //        {
        //            r = m_pTexBuffer[j * width * 4 + i * 4];
        //            g = m_pTexBuffer[j * width * 4 + i * 4 + 1];
        //            b = m_pTexBuffer[j * width * 4 + i * 4 + 2];
        //            a = m_pTexBuffer[j * width * 4 + i * 4 + 3];

        //            bm.SetPixel(i, height - 1 - j, Color.FromArgb(a, r, g, b));
        //        }
        //    }

        //    return bm;
        }

        #endregion FileIO
    }
}
